/* The file is saved in UTF-8 codepage.
 * Check: «Stereotype», Section mark-§, Copyright-©, Alpha-α, Beta-β, Smile-☺
 */
package cz.pecinovsky.english.lootp.manager;

/*
 * Preproceosr variables in use:
 *      @DB_CM      - Canvas manager is debugged
 *      @IAdaptable - Adaptable objects must register themselves
 */


////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ End of ignored starting test - place for imports +++++

//%A+ >=133x
import cz.pecinovsky.english.lootp.utility.NamedColor;
//%A-
//%A+ >135
import cz.pecinovsky.english.lootp.utility.Clock;
import cz.pecinovsky.english.lootp.utility.ITimed;
//%A-
//%A+ >=133x
import cz.pecinovsky.english.lootp.utility.IO;
import cz.pecinovsky.english.lootp.utility.Position;
import cz.pecinovsky.english.lootp.utility.Size;
//%A-

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;

import java.awt.event.KeyListener;
import java.awt.event.MouseListener;

import java.awt.image.BufferedImage;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

import java.util.ArrayList;
//%I+ @IAdaptable >135
//import java.util.Collection;
//%I-
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Properties;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.imageio.ImageIO;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

//%X+ xxxxx Start of the ovejumped text with declaration of the envelope xxxxx
/*******************************************************************************
 *<pre>
 * After enveloping before removing locks
 * 
 * Previous:  No - this is a newly defined type
 *            However, it replaces the previously used class
 *            {@link cz.pecinovsky.english.lootp.shapes.Canvas}.
 *
 * Following: No
 *</pre>
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.03.2017 — 2010-11-08
 */
public class CanvasManager_Envelope1
{    private CanvasManager_Envelope1() {}    static
////////////////////////////////////////////////////////////////////////////////
//%X- ----- End of the ovejumped text with declaration of the envelope -----


/*******************************************************************************
 * Třída <b><code>CanvasManager</code></b> slouží k jednoduchému kreslení
 * na virtuální plátno a připadné následné animaci nakreslených obrázků.
 * </p><p>
 * Třída neposkytuje veřejný konstruktor, protože chce, aby její instance
 * byla singleton, tj. aby se všechno kreslilo na jedno a to samé plátno.
 * Jediným způsobem, jak získat odkaz na instanci třídy
 * <code>CanvasManager</code>,
 * je volaní její statické metody <code>getInstance()</code>.
 * </p><p>
 * Třída <code>CanvasManager</code> funguje jako manažer, který dohlíží
 * na to, aby se po změně zobrazení některého z tvarů všechny ostatní tvary
 * řádně překreslily, aby byly správně zachyceny všechny překryvy
 * a aby se při pohybu jednotlivé obrazce vzájemně neodmazavaly.
 * Aby vše správně fungovalo, je možno použít jeden dvou přístupů:</p>
 * <ul>
 *   <li>Manažer bude obsah plátna překreslovat
 *       <b>v pravidelných intervalech</b>
 *       bez ohledu na to, jestli se na něm udála nějaká změna či ne.
 *       <ul><li>
 *       <b>Výhodou</b> tohoto přístupu je, že se zobrazované objekty
 *       nemusí starat o to, aby se manažer dozvěděl, že se jejich state změnil.
 *       </li><li>
 *       <b>Neýhodou</b> tohoto přístupu je naopak to, že manažer
 *       spotřebovává na neustálé překreslování jistou část výkonu
 *       procesoru, což může u pomalejších počítačů působit problémy.
 *       <br>&nbsp;</li></ul></li>
 *   <li>Manažer překresluje plátno <b>pouze na výslovné požádání</b>.
 *       <ul><li>
 *       <b>Výhodou</b> tohoto přístupu je úspora spotřebovaného výkonu
 *       počítače v období, kdy se na plátně nic neděje.
 *       </li><li>
 *       <b>Nevýhodou</b> tohoto přístupu je naopak to, že kreslené
 *       objekty musí na každou změnu svého stavu upozornit manažera,
 *       aby věděl, žed má plátno překreslit.
 *   </li>
 * </ul><p>
 * Třída <code>CanvasManager</code> požívá druhou z uvedených strategií,
 * tj. <b>překresluje plátno pouze na požádání</b>.
 * </p><p>
 * Obrazec, který chce být zobrazován na plátně, se musí nejprve přihlásit
 * u instance třídy <code>CanvasManager</code>, aby jej tato zařadila
 * mezi spravované obrazce (sada metod <code>add&hellip;</code>).
 * Přihlásit se však mohou pouze instance tříd, které implementují
 * rozhraní <code>IPaintable</code>.
 * </p><p>
 * Nepřihlášený obrazec nemá šanci býti zobrazen, protože na plátno
 * se může zobrazit pouze za pomoci kreslítka, jež může získat jedině od
 * instance třídy <code>CanvasManager</code>, ale ta je poskytuje pouze
 * instancím, které se přihlásily do její správy.
 * </p><p>
 * Obrazec, který již dále nemá byt kreslen, se muže odhlásit zavoláním
 * metody <code>remove(IPaintable)</code>.Zavoláním metody
 * <code>removeAll()</code> se ze seznamu spravovaných (a tím i z plátna)
 * odstraní všechny vykreslované obrazce.
 * </p><p>
 * Efektivitu vykreslování je možné ovlivnit voláním metody
 * <code>dontDraw()</code>, která pozastaví překreslování plátna po nahlášených
 * změnách. Její volání je výhodné např. v situaci, kdy je třeba vykreslit
 * obrazec složený z řady menších obrazců a bylo by nevhodné překreslovat
 * plátno po vykreslení každého z nich.
 * </p><p>
 * Do původního stavu převedeme plátno voláním metody <code>returnDraLevel()</code>,
 * která vrátí vykreslování do stavu před posledním voláním metody
 * <code>dontDraw()</code>. Nemůžeč se tedy stát, že by se při zavolání metody
 * <code>dontDraw()</code> v situaci, kdy je již vykreslování pozastaveno,
 * začalo po následém zavolání <code>returnDraLevel()</code> hned vykreslovat.
 * Po dvou voláních <code>returnDraLevel()</code> se začne vykreslovat až po
 * dvou zavoláních <code>returnDraLevel()</code>.
 * </p><p>
 * Proto plátno pouze žádáme, aby se vrátilo do toho kreslícího stavu,
 * ve kterém bylo v okamžiku, kdy jsme je naposledy žádali o to,
 * aby se přestalo překreslovat. Nemůže se tedy stát, že by se při zavolání
 * metody <code>dontDraw()</code> v situaci, kdy je již vykreslování
 * pozastaveno, začalo po následném zavolání <code>returnDraLevel()</code> hned
 * vykreslovat.
 * </p><p>
 * Každé zavolání metody <code>dontDraw()</code> musí být doplněno
 * odpovídajícím voláním <code>returnDraLevel()</code>. Teprve když poslední
 * <code>returnDraLevel()</code> odvolá první <code>dontDraw()</code>, bude
 * překreslování opět obnoveno.
 * </p>
 *
 * @author Rudolf PECINOVSKÝ
 * @version 6.04.2017 — 2011-02-04
 */
public final class CanvasManager
{
//    static { System.out.println("Inicializace třídy CanvasManager"); }
//    { System.out.println("Inicializace instance třídy CanvasManager"); }
//== CONSTANT CLASS ATTRIBUTES =================================================

    /** Application window title. */
    private static final String TITLE_0  = "Managed canvas";

    /** Starting canvas field width. */
    private static final int WIDTH_0 = 6;

    /** Starting canvas field height. */
    private static final int HEIGHT_0 = 6;

    /** Starting canvas background color. */
    private static final NamedColor BACKGROUNG_0 = NamedColor.CREAMY;

    /** Starting grid color. */
    private static final NamedColor GRID_COLOR_0 = NamedColor.BLACK;

    /** Default grid-lines spacing (field siza). */
    private static final int STEP_0 = 50;

    /** Maximal allowed grid-lines spacing (field siza). */
    private static final int MAX_KROK = 200;

    /** The only instance of the class - singleton. */
    private static final CanvasManager CM;

    /** Zámek zabezepčující plynulou funkci grafiky. */
    private static final Lock LOCK = new ReentrantLock();



//== VARIABLE CLASS ATTRIBUTES =================================================
//== STATIC CONSTRUCTOR (CLASS INITIALIZER, STATIC INICITALIZING BLOCK) ========

    static
    {
        CM = new CanvasManager();

        //Při kreslení čar se ptá APosuvný po správci plánta. Proto se mohou
        //čáry kreslit (a tím i volat metoda setStepSize(int,int,int))
        //až poté, co bude jednináček (konstanta CM) inicializován.

        Point position = configurationFromFile();

//        LOCK.lock();
//        try
//        {
//%I+ @DB_CM
//          System.out.println("CanvasManager created");
//%I-
            //Připraví a vykreslí prázdné plátno (souřadnice jsou již načteny)
            CM.setPosition(position.x, position.y);
//%I+ @DB_CM
//            System.out.println("Position set");
//%I-
            CM.setStepSize(STEP_0, WIDTH_0, HEIGHT_0);
//%I+ @DB_CM
//            System.out.println("Step and size set");
//%I-
            //Nyní je znám rozměr plátna, tak můžeme umístit dialogová okna
            int x = CM.window.getX();
            int y = CM.window.getY() + CM.window.getHeight();
            IO.setDialogsPosition(x, y);
//%I+ @DB_CM
//            System.out.println("IO set");
//%I-
            CM.setName(TITLE_0);
//        }
//        finally
//        {
//            LOCK.unlock();
//        }
    }



//== CONSTANT INSTANCE ATTRIBUTES ==============================================

    /** Aplikační okno animačního plátna. */
    private final JFrame window;

    /** Instance lokální třídy, která je zřízena proto, aby odstínila
     *  metody svého rodiče JPanel. */
    private final JPanel canvas;

    /** Seznam zobrazovaných předmětů. */
    private final List<IPaintable> paintedObjects = new ArrayList<IPaintable>();
//%I+ @IAdaptable >135
//
//    /** Seznam přihlášených přizpůsobivých objektů. */
//    private final Collection<IAdaptable> adaptables =
//                                            new ArrayList<IAdaptable>();
//%I-



//== VARIABLE INSTANCE ATTRIBUTES===============================================

    //Z venku neovlivnitelné Atributy pro zobrazeni plátna v aplikačním okně

        /** Vše se kreslí na obraz - ten se snadněji překreslí. */
        private Image canvasImage;

        /** Painter získané od obrazu plátna, na nějž se vlastně kreslí. */
        private Painter painter;

        /** Semafor bránící příliš častému překreslování. Překresluje se pouze
         *  je-li ==0. Nesmi byt <0. */
        private int drawLevel = 0;

        /** Příznak toho, že kreslení právě probíhá,
         *  takže vypínání nefunguje. */
        private boolean drawing = false;

        /** Čáry zobrazující na plántě mřížku. */
        private Line[] horizontals,   verticals;

    //Přímo ovlivnitelné atributy

        /** Rozteč čtvercové sítě. */
        private int step = STEP_0;

        /** Zobrazuje-li se mřížka. */
        private boolean showGrid = true;

        /** NamedColor pozadí plátna. */
        private NamedColor backgroundColor = BACKGROUNG_0;

        /** NamedColor čar mřížky. */
        private NamedColor gridColor = GRID_COLOR_0;

        /** Šířka current plochy plátna v udávaná v polích. */
        private int fieldWidth = WIDTH_0;

        /** Výška current plochy plátna v udávaná v polích. */
        private int fieldHeight = HEIGHT_0;

        /** Šířka current plochy plátna v bodech. */
        private int pixelWidth = WIDTH_0 * step;

        /** Výška current plochy plátna v bodech. */
        private int pixelHeight = HEIGHT_0 * step;
//%A+ >135

        /** Zda se mají adaptables upozorňovat na změny rozměru pole. */
        private boolean notifySizeChanges = true;
//%A-

        /** Zda je možno měnit velikost kroku. */
        private Object stepSizeOwner = null;

        /** Position plátna na obrazovace - při používání více obrazovek
         *  je občas třeba ji po zviditelnění obnovit. */
        Point position;



//== CLASS GETTERS AND SETTERS =================================================
//== OTHER NON-PRIVATE CLASS METHODS ===========================================

//##############################################################################
//== CONSTUCTORS AND FACTORY METHODS============================================

    /***************************************************************************
     * Jediná metoda umožnující získat odkaz na instanci plátna.
     * Vrací vsak pokaždé odkaz na stejnou instanci,
     * protože instance plátna je singleton.
     * <p>
     * Pokud instance při volaní metody ještě neexistuje,
     * metoda instanci vytvoří.</p>
     *
     * @return Instance třídy {@code CanvasManager}
     */
    public static CanvasManager getInstance()
    {
        return CM;
    }


    /***************************************************************************
     * Vytvoří instanci třídy - jedináčka => je volán pouze jednou.
     */
    @SuppressWarnings("serial")
    private CanvasManager()
    {
        window = prepareWindow();

//%I+ @DB_CM
//        System.out.println("Window prepared");
//%I-
        //Vlastní plátno je definováno jako instance anonymní třídy
        canvas =
            new JPanel()
            {   /** Povinně překrývaná abstraktní metoda třídy JPanel. */
                @Override
                public void paintComponent(Graphics g)
                {
                    if (!LOCK.tryLock()) {
                        try {
                            LOCK.notifyAll();
                        }
                        catch(java.lang.IllegalMonitorStateException Ex) {
                            //Stalo se neco divneho, skoncim tento pokus
                            //o prekresleni a zkusim to znova;
                            Runnable updateAComponent = new Runnable() {
                                @Override
                                public void run() { canvas.repaint(); }
                            };
                            SwingUtilities.invokeLater(updateAComponent);
                            return;
                        }
                    }
                    try {
                        g.drawImage(canvasImage, 0, 0, null);
                    }
                    finally {
                        LOCK.unlock();
                    }
                }
            };//Konec definice třídy plátna
//%I+ @DB_CM
//        System.out.println("Canvas vytvořeno");
//%I-
        window.setContentPane(canvas);
//%I+ @DB_CM
//        System.out.println("Canvas přidáno");
//%I-
    }



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================

    /***************************************************************************
     * Nastaví rozměr plátna zadáním bodové velikosti políčka a
     * počtu políček ve vodorovném a svislém směru.
     * Při velikosti políčka = 1 se vypíná zobrazování mřížky.
     *
     * @param  step      Nová bodová velikost políčka
     * @param  rowSize  Nový počet políček vodorovně
     * @param  colCount  Nový počet políček svisle
     */
    public void setStepSize(int step, int rowSize, int colCount)
    {
        LOCK.lock();
        try {
            setStepSize(step, rowSize, colCount, null);
        }
        finally {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Nastaví rozměr plátna zadáním bodové velikosti políčka a
     * počtu políček ve vodorovném a svislém směru.
     * Při velikosti políčka = 1 se vypíná zobrazování mřížky.
     *
     * @param  step      Nová bodová velikost políčka
     * @param  rowSize  Nový počet políček vodorovně
     * @param  colCount  Nový počet políček svisle
     * @param  changer   Objekt, který žádá o změnu rozměru. Jakmile je jednou
     *                   tento objekt nastaven, nesmí již rozměr plátna
     *                   měnit nikdo jiný.
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void setStepSize(int step, int rowSize, int colCount, Object changer)
    {
//%I+ @DB_CM
//        System.out.println("setKrokRozmer - vstup");
//%I-
        LOCK.lock();
        try {
            //Kontrola, jestli rozměry mění ten, kdo je měnit smí
            if ((changer != null)  &&
                (changer != stepSizeOwner))
            {
                if (stepSizeOwner == null) {
                    stepSizeOwner = changer;
                } else {
                    throw new IllegalStateException(
                        "Změna kroku a rozměru není danému objektu povolena");
                }
            }
            //Ověření korektnosti zadaných parametrů
            Dimension obrazovka = Toolkit.getDefaultToolkit().getScreenSize();
            if ((step   < 1)  ||
                (rowSize < 2)  ||  (obrazovka.width  < pixelWidth) ||
                (colCount < 2)  ||  (obrazovka.height < pixelHeight))
            {
                throw new IllegalArgumentException(
                    "\nŠpatně zadané rozměry: " +
                    "\n  krok =" + step  + " bodů," +
                    "\n  šířka=" + rowSize + " polí = " + rowSize*step + " bodů," +
                    "\n  výška=" + colCount + " polí = " + colCount*step + " bodů," +
                    "\n  obrazovka= " + obrazovka.width  + "×" +
                                        obrazovka.height + " bodů\n");
            }

            pixelWidth = rowSize * step;
            pixelHeight = colCount * step;

            window.setResizable(true);
            canvas.setPreferredSize(new Dimension(pixelWidth, pixelHeight));
            window.pack();
            window.setResizable(false);

            canvasImage = canvas.createImage(pixelWidth, pixelHeight);
            painter   = new Painter((Graphics2D)canvasImage.getGraphics());
            painter.setBackground(backgroundColor);

            int old          = this.step;
            this.step        = step;
            this.fieldWidth  = rowSize;
            this.fieldHeight = colCount;

            prepareGrid();

//%A+ >135
            if (notifySizeChanges  &&  (step != old)) {
                drawLevel++; {
//%A-
//%A+ @IAdaptable >135
//                    for (IAdaptable ip : adaptables) {
//                        ip.stepChanged(starý, krok);
//                    }
//%A-
//%A+ >135
                    for (IPaintable ik : listOfPainted()) {
                        if (ik instanceof IAdaptable) {
                            ((IAdaptable)ik).stepChanged(old, step);
                        }
                    }
                }drawLevel--;
            }
//%A-
            IO.Correction.windowSize(window);
            IO.Correction.windowLocation(window);
//%I+ @DB_CM
//            System.out.println("setStepSize - pred nastavenim viditelnosti");
//%I-
            setVisible(true);
            redraw();
//%I+ @DB_CM
//            System.out.println("setStepSize - po nastaveni viditelnosti");
//            redraw();  //Překresluje již předchozí metoda
//            System.out.println("setStepSize - po prekresleni");
//%I-
        }
        finally {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Vrátí vzdálenost čar mřížky = bodovou velikost políčka.
     *
     * @return Bodová velikost políčka
     */
     public int getStep()
     {
         return step;
     }


    /***************************************************************************
     * Nastaví vzdálenost čar mřížky = bodovou velikost políčka.
     * Při velikosti políčka = 1 se vypíná zobrazování mřížky.
     *
     * @param velikost  Nová bodová velikost políčka
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void setStep(int velikost)
    {
        LOCK.lock();
        try
        {
            setStepSize(velikost, fieldWidth, fieldHeight);
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Vrátí počet sloupců plátna, tj. jeho políčkovou šířku.
     *
     * @return  Aktuální políčková width plátna (počet políček vodorovně)
     */
    public int getFieldWidth()
    {
        return fieldWidth;
    }


    /***************************************************************************
     * Vrátí bodovou šířku plátna.
     *
     * @return  Aktuální bodová width plátna (počet bodů vodorovně)
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    int getPixelWidth()
    {
        return pixelWidth;
    }


    /***************************************************************************
     * Vrátí počet řádků plátna, tj. jeho políčkovou výšku.
     *
     * @return  Aktuální políčková height plátna (počet políček svisle)
     */
    public int getFieldHeight()
    {
        return fieldHeight;
    }


    /***************************************************************************
     * Vrátí bodovou výšku plátna.
     *
     * @return  Aktuální bodová height plátna (počet bodů svisle)
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    int getPixelHeight()
    {
        return pixelHeight;
    }

//%A+ >135

    /***************************************************************************
     * Vrátí políčkový rozměr plátna, tj. šířku a výšku v polích.
     *
     * @return  Aktuální políčkový rozměr plátna
     */
    public Size getSize()
    {
        return new Size(fieldWidth, fieldHeight);
    }

//%A-

    /***************************************************************************
     * Nastaví rozměr plátna zadáním jeho políčkové výsky a šířky.
     *
     * @param  colCount  Nový počet políček vodorovně
     * @param  rowSize    Nový počet políček svisle
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void setSize(int colCount, int rowSize)
    {
        LOCK.lock();
        try
        {
            setStepSize(step, colCount, rowSize);
        }
        finally
        {
            LOCK.unlock();
        }
    }

//%A+ >135

    /***************************************************************************
     * Nastaví rozměr plátna zadáním jeho políčkové výsky a šířky.
     *
     * @param  size  Zadávaný rozměr v počtu políček
     */
    public void setSize(Size size)
    {
        LOCK.lock();
        try
        {
            setSize(size.width, size.height);
        }
        finally
        {
            LOCK.unlock();
        }
    }

//%A-

    /***************************************************************************
     * Vrátí informaci o tom, je-li zobrazována mřížka.
     *
     * @return Mřížka je zobrazována = true, není zobrazována = false.
     */
    public boolean isGridVisible()
    {
    	return showGrid;
    }


    /***************************************************************************
     * V závislosti na hodntě parametru nastaví nebo potlačí
     * zobrazování čar mřížky.
     *
     * @param visible  Jestli mřížku zobrazovat.
     */
    public void setGridVisible(boolean visible)
    {
        LOCK.lock();
        try
        {
            showGrid = visible;
            prepareGrid();
            redraw();
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Poskytuje informaci o aktuální viditelnosti okna.
     *
     * @return Je-li okno viditelné, vrací <b>true</b>, jinak vrací <b>false</b>
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    boolean isVisible()
    {
        return window.isVisible();
    }


    /***************************************************************************
     * V závislosti na hodnotě svého parametru
     * nastaví nebo potlačí viditelnost plátna na displeji.
     *
     * @param visible logická hodnota požadované viditelnost (true=viditelné)
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void setVisible(boolean visible)
    {
        boolean repainted = false;
        boolean change    = (isVisible() != visible);
        if (change)
        {
            position = window.getLocation();
//%I+ @DB_CM
//            System.out.println("Před okno.setVisible(viditelné)");
//%I-
            window.setVisible(visible);
            if (visible)
            {
                //Na WinXP při více obrazovkách po zviditelnění blblo =>
                //bylo třeba znovu nastavit pozici
                window.setLocation(position);

                window.setAlwaysOnTop(true);
                window.toFront();
                redraw();
                window.setAlwaysOnTop(false);
            }
//%I+ @DB_CM
//            System.out.println("Za okno.setVisible(visible)");
//%I-
        }
    }


    /***************************************************************************
     * Vrátí aktuální barvu pozadí.
     *
     * @return  Nastavena color pozadí
     */
    public NamedColor getBackgroundColor()
    {
        return backgroundColor;
    }


    /***************************************************************************
     * Nastaví pro plátno barvu pozadí.
     *
     * @param  color  Nastavovaná color pozadí
     */
    public void setBackgroundColor(NamedColor color)
    {
        backgroundColor = color;
        painter.setBackground(backgroundColor);
        redraw();
    }


    /***************************************************************************
     * Pomocná metoda pro účely ladění aby bylo možno zkontrolovat,
     * ze na konci metody má semafor stejnou hodnotu, jako měl na počátku.
     *
     * @return  Stav vnitřního semaforu: >0  - nebude se kreslit,<br>
     *                                   ==0 - kreslí se,<br>
     *                                   <0  - chyba
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    int getDrawLevel()
    {
        return drawLevel;
    }


    /***************************************************************************
     * Vrátí aktuální name v titulkové liště okna plátna.
     *
     * @return  Aktuální name okna
     */
    public String getName()
    {
        return window.getTitle();
    }


    /***************************************************************************
     * Nastaví name v titulkové liště okna plátna.
     *
     * @param name  Nastavovaný name
     */
    public void setName(String name)
    {
        window.setTitle(name);
    }

//%A+ >135

    /***************************************************************************
     * Vrátí vodorovnou souřadnici aplikačního okna plátna.
     *
     * @return Position levého horního rohu aplikačního okna plátna.
     */
    public Position getPosition()
    {
        return new Position(window.getX(), window.getY());
    }

//%A-

    /***************************************************************************
     * Nastaví pozici aplikačního okna aktivního plátna na obrazovce.
     *
     * @param x  Vodorovná souřadnice aplikačního okna plátna.
     * @param y  Svislá souřadnice aplikačního okna plátna.
     */
    public void setPosition(int x, int y)
    {
        window.setLocation(x, y);
        position = new Point(x, y);
    }

//%A+ >135

    /***************************************************************************
     * Nastaví pozici aplikačního okna aktivního plátna na obrazovce.
     *
     * @param position  Požadovaná pozice aplikačního okna plátna na obrazovce.
     */
    public void setPosition(Position position)
    {
        window.setLocation(position.getX(), position.getY());
    }


    /***************************************************************************
     * Vrátí instanci třídy <code>Picture</code> zobrazující zadaný výřez
     * AktivníhoPlátna.
     * @param x     Vodorovná pozice požadovaného výřezu
     * @param y     Svislá pozice požadovaného výřezu
     * @param width Šířka požadovaného výřezu v bodech
     * @param height Výška požadovaného výřezu v bodech
     * @return Instance třídy <code>Image</code> zobrazující zadaný výřez
     */
    public Picture getPicture(int x, int y, int width, int height)
    {
        BufferedImage bim = getBufferedImage(x, y, width, height);
        return new Picture(0, 0, bim);
    }

//%A-


//== OTHER NON-PRIVATE INSTANCE METHODS ========================================

    /***************************************************************************
     * Převede instanci na řetězec. Používá se především při ladění.
     *
     * @return Řetězcová reprezentace dané instance.
     */
    @Override
    public String toString()
    {
        return getClass().getName() + "(krok=" + step +
                ", šířka=" + fieldWidth + ", výška=" + fieldHeight +
                ", pozadí=" + backgroundColor + ")";
    }


    /***************************************************************************
     * Vykreslí všechny elementy.
     */
    public void redraw()
    {
        LOCK.lock();
        try
        {
            if (drawing) {    //Právě překresluji - volám nepřímo sám sebe
                return;
            }
            if ((drawLevel == 0)  &&  isVisible())   //Mám kreslit a je proč
            {
                drawing = true;
                painter.fillRectangle(0, 0, pixelWidth, pixelHeight,
                                    backgroundColor);
                if (showGrid  &&  (gridColor != backgroundColor))
                {
                    //Budeme kreslit mřížku -- bude pod obrazci
                    for (int i=0;   i < fieldWidth;) {
                        verticals[i++].paint(painter);
                    }
                    for (int i=0;   i < fieldHeight;) {
                        horizontals[i++].paint(painter);
                    }
                }
                for (IPaintable shape : paintedObjects) {
                    shape.paint(painter);
                }

                //Aktualizované plátno se překreslí
                Runnable updateAComponent = new Runnable() {
                    @Override
                    public void run() { canvas.repaint(); }
                };
                SwingUtilities.invokeLater(updateAComponent);

                drawing = false;        //Už nekreslím
            }
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Potlačí překreslování plátna, přesněji zvýší hladinu potlačení
     * překreslování o jedničku. Návratu do stavu před voláním této metody
     * se dosáhne zavoláním metody <code>returnDraLevel()</code>.</p>
     * <p>
     * Metody <code>dontDraw()</code> a <code>returnDraLevel()</code>
     * se tak chovají obdobně jako závorky, mezi nimiž je vykreslování
     * potlačeno.</p>
     */
    public void dontDraw()
    {
        LOCK.lock();
        try
        {
            drawLevel++;
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Vrátí překreslování do stavu před posledním voláním metody
     * <code>dontDraw()</code>. Předcházelo-li proto více volání metody
     * <code>dontDraw()</code>, začne se překreslovat až po odpovídajím počtu
     * zavolání metody <code>returnDraLevel()</code>.
     *
     * @throws IllegalStateException
     *         Je-li metoda volána aniž by předcházelo odpovídající volání
     *         <code>dontDraw()</code>.
     */
    public void returnDraLevel()
    {
        LOCK.lock();
        try
        {
            if (drawLevel == 0) {
                throw new IllegalStateException(
                    "Vrácení do stavu kreslení musí přecházet zákaz!");
            }
            drawLevel--;
            if (drawLevel == 0)  {
                redraw();
            }
        }
        catch(IllegalStateException ex)
        {
            throw ex;
        }
        finally
        {
            LOCK.unlock();
        }
    }

//%A+ >135

    /***************************************************************************
     * Odpojí se od hodin a přestává synchronizovat
     * své překreslování s jejich tikotem.
     */
    public void clockOff()
    {
        Clock clock = Clock.getInstance();
        clock.setHeader (null);
        clock.setFooter(null);
    }


    /***************************************************************************
     * Napojí se na hodiny, aby se na začátku tiku vypnulo kreslení
     * a na konci tiku opět zapnulo.
     */
    public void clockOn()
    {
        Clock clock = Clock.getInstance();
        clock.setHeader(new ITimed() {
            @Override
            public void tick() { dontDraw(); }
        });
        clock.setFooter(new ITimed() {
            @Override
            public void tick() { returnDraLevel(); }
        });
    }

//%A-

    /***************************************************************************
     * Odstraní zadaný obrazec ze seznamu malovaných.
     * Byl-li obrazec v seznamu, překreslí plátno.
     *
     * @param shape  Odstraňovaný obrazec
     *
     * @return  true v případě, když obrazec v seznamu byl,
     *          false v případě, když nebylo co odstraňovat
     */
    public boolean remove(IPaintable shape)
    {
        LOCK.lock();
        try {
            boolean ret = paintedObjects.remove(shape);
            if (ret) {
                redraw();
            }
            return ret;
        }
        finally {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Vyčisti plátno, tj. vyprázdní seznam malovaných
     * (odstraní z něj všechny obrazce).
     */
    public void removeAll()
    {
        LOCK.lock();
        try {
            dontDraw(); {
                ListIterator<IPaintable> it = paintedObjects.listIterator();
                while (it.hasNext()) {
                    it.next();
                    it.remove();
                }
            } returnDraLevel();
        }
        finally {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Není-li zadaný obrazec v seznamu malovaných, přidá jej na konec
     * (bude se kreslit jako poslední, tj. na vrchu.
     * Byl-li obrazec opravdu přidán, překreslí plátno.
     * Objekty budou vždy kresleny v orderOf, v němž byly přidány do správy,
     * tj. v seznamu parametrů zleva doprava
     * a dříve zaregistrované objekty před objekty zaregistrovanými později.
     *
     * @param  shapes  Přidávané obrazce
     * @return  Počet skutečně přidaných obrazců
     */
    public int add(IPaintable... shapes)
    {
        LOCK.lock();
        try
        {
            int count = 0;
            dontDraw(); {
                for (IPaintable ip : shapes)
                {
                    if (! paintedObjects.contains(ip)) {
                        paintedObjects.add(ip);
                        count++;
                    }
                }
            } returnDraLevel();
            return count;
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * nad zadaným obrazcem.
     * Pokud již v seznamu byl, jenom jej přesune do zadané pozice.
     *
     * @param  presentShape  Obrazec, který má byt při kreslení pod
     *                    přidávaným obrazcem
     * @param  addedShape   Přidávaný obrazec
     *
     * @return  {@code true} v případě, když byl obrazec opravdu přidán,
     *          {@code false} v případě, když již mezi zobrazovanými byl
     *          a pouze se přesunul do jiné urovné
     */
    public boolean addOver(IPaintable presentShape, IPaintable addedShape)
    {
        LOCK.lock();
        try
        {
            boolean newMember = ! paintedObjects.remove(addedShape);
            int where = paintedObjects.indexOf(presentShape);
            if (where < 0)
            {
                throw new IllegalArgumentException(
                    "Referenční objekt není na plátně zobrazován!");
            }
            paintedObjects.add(where+1, addedShape);
            redraw();
            return newMember;
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * pod zadaným obrazcem.
     * Pokud již v seznamu byl, jenom jej přesune do zadané pozice.
     *
     * @param  presentShape  Obrazec, který má byt při kreslení nad
     *                   přidávaným obrazcem
     * @param  addedShape   Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
    public boolean addUnder(IPaintable presentShape, IPaintable addedShape)
    {
        LOCK.lock();
        try
        {
            boolean newMember = ! paintedObjects.remove(addedShape);
            int where = paintedObjects.indexOf(presentShape);
            if (where < 0)
            {
                throw new IllegalArgumentException(
                    "Referenční objekt není na plátně zobrazován!");
            }
            paintedObjects.add(where, addedShape);
            redraw();
            return newMember;
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * nad všemi obrazci.
     * Pokud již v seznamu byl, jenom jej přesune do požadované pozice.
     *
     * @param  addedShape   Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    boolean addAtTop(IPaintable addedShape)
    {
        LOCK.lock();
        try
        {
            boolean nebyl = ! paintedObjects.remove(addedShape);
            paintedObjects.add(addedShape);
            redraw();
            return nebyl;
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * pod zadaným obrazcem.
     * Pokud již v seznamu byl, jenom jej přesune do zadané pozice.
     *
     * @param  addedShape   Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    boolean addToBottom(IPaintable addedShape)
    {
        LOCK.lock();
        try
        {
            boolean nebyl = ! paintedObjects.remove(addedShape);
            paintedObjects.add(0, addedShape);
            redraw();
            return nebyl;
        }
        finally
        {
            LOCK.unlock();
        }
    }


    /***************************************************************************
     * Vrátí orderOf zadaného prvku v seznamu kreslených prvků.
     * Prvky se přitom kreslí v rostoucím orderOf, takže obrazec
     * s větším poradím je kreslen nad obrazcem s menším poradím.
     * Neni-li zadaný obrazec mezi kreslenými, vrátí -1.
     *
     * @param  obrazec  Objekt, na jehož kreslicí orderOf se dotazujeme
     *
     * @return  Pořadí obrazce; prvý kresleny obrazec má orderOf 0.
     *          Neni-li zadaný obrazec mezi kreslenými, vrátí -1.
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    int orderOf(IPaintable obrazec)
    {
        return paintedObjects.indexOf(obrazec);
    }


    /***************************************************************************
     * Vrátí nemodifikovatelný seznam všech spravovaných obrázků.
     *
     * @return  Požadovaný seznam
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    List<IPaintable> listOfPainted()
    {
        return Collections.unmodifiableList(paintedObjects);
    }

//%I+ @IAdaptable >135
//
//    /***************************************************************************
//     * Přihlásí posluchače změny velikosti pole.
//     *
//     * @param  posluchač  Přihlašovaný posluchač
//     * @return  Informace o tom, byl-li posluchač doopravdy přidán -
//     *          false oznamuje, že posluchač už byl přihlášen
//     *          a nebylo jej proto třeba přidávat.
//     */
//    public boolean addAdaptable(IAdaptable posluchač)
//    {
//        return adaptables.add(posluchač);
//    }
//
//
//    /***************************************************************************
//     * Odhlásí posluchače změny velikosti pole.
//     *
//     * @param  posluchač  Odhlašovaný posluchač
//     * @return  Informace o tom, byl-li posluchač doopravdy odebrán -
//     *          false oznamuje, že posluchač už nebyl přihlášen
//     *          a nebylo jej proto třeba odebírat.
//     */
//    public boolean removeAdaptable(IAdaptable posluchač)
//    {
//        return adaptables.remove(posluchač);
//    }
//
//%I-
//%A+ >135

    /***************************************************************************
     * Nastaví, zda se mají přihlášeným posluchačům hlásit změny
     * velikosti kroku a vrátí původní nastavení.
     *
     * @param hlásit  Požadované nastavení (true=hlásit, false=nehlasit).
     * @return Původní nastavení
     */
    public boolean referSizeChanges(boolean hlásit)
    {
        boolean ret = notifySizeChanges;
        notifySizeChanges = hlásit;
        return ret;
    }

//%A-

    /***************************************************************************
     * Přihlásí posluchače událostí klávesnice.
     *
     * @param posluchač  Přihlašovaný posluchač
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void addKeyboardListener(KeyListener posluchač)
    {
        window.addKeyListener(posluchač);
    }


    /***************************************************************************
     * Odhlásí posluchače klávesnice.
     *
     * @param posluchač  Odhlašovaný posluchač
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void removeKeyboardListener(KeyListener posluchač)
    {
        window.removeKeyListener(posluchač);
    }


    /***************************************************************************
     * Přihlásí posluchače událostí myši.
     *
     * @param posluchač  Přihlašovaný posluchač
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void addMouseListener(MouseListener posluchač)
    {
        window.addMouseListener(posluchač);
    }


    /***************************************************************************
     * Odhlásí posluchače myši.
     *
     * @param posluchač  Odhlašovaný posluchač
     */
//%I+ <135
//    private
//%I-
//%A+ >135
    public
//%A-
    void removeMouseListener(MouseListener posluchač)
    {
        window.removeMouseListener(posluchač);
    }

//%A+ >135

    /***************************************************************************
     * Uloží obraz aktivního plátna do zadaného souboru.
     *
     * @param soubor Soubor, do nějž se má obraz plátna uložit
     */
    public
    void saveAsPicture(File soubor)
    {
        BufferedImage bim = getBufferedImage();
        try {
            ImageIO.write(bim, "PNG", soubor);
        } catch(IOException exc)  {
            throw new RuntimeException(
            	"\nObraz aktivního plátna se nepodařilo uložit do souboru " +
                soubor,  exc);
        }
    }

//%A-

//== PRIVATE AND AUXILIARY CLASS METHODS =======================================

    /***************************************************************************
     * Přečte parametry z konfiguračního souboru.
     * Tento soubor je umístěn v domovském adresáři uživatele
     * ve složce {@code .rup} v souboru {@code bluej.properties}.
     *
     * @return
     */
    private static Point configurationFromFile() {
        Point position;
        Properties sysProp = System.getProperties();
        String     userDir = sysProp.getProperty("user.home");
        File       rupFile = new File(userDir, ".rup/bluej.properties");
        Properties rupProp = new Properties();
        try {
            Reader reader = new FileReader(rupFile);
            rupProp.load(reader);
            reader.close();
            String sx = rupProp.getProperty("canvas.x");
            String sy = rupProp.getProperty("canvas.y");
            int x = Integer.parseInt(rupProp.getProperty("canvas.x"));
            int y = Integer.parseInt(rupProp.getProperty("canvas.y"));
            position = new Point(x, y);
        }catch(Exception e)  {
            position = new Point(0, 0);
        }
        return position;
    }



//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================
//%A+ >135

    /***************************************************************************
     * Vrátí obrázek na aktivním plátně.
     * @return Obsah plátna jako obrázek
     */
    private BufferedImage getBufferedImage()
    {
        if (canvasImage instanceof BufferedImage) {
            return (BufferedImage) canvasImage;         //==========>
        }
        else {
            return getBufferedImage(0, 0, pixelWidth, pixelHeight);
        }
    }


    /***************************************************************************
     * Vrátí obrázek výřezu na aktivním plátně.
     *
     * @param x
     * @param y
     * @param šířka
     * @param výška
     * @return Výřez obsahu plátna jako obrázek
     */
    private BufferedImage getBufferedImage(int x, int y, int šířka, int výška)
    {
        BufferedImage ret = new BufferedImage(šířka, výška,
                                              BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D)ret.getGraphics();
        g2d.drawImage(canvasImage, -x, -y, Painter.IMG_OBSERVER);
        return ret;
    }

//%A-

    /***************************************************************************
     * Připraví čáry vyznačující jednotlivá pole aktivního plátna.
     * Pokud se čáry kreslit nemají, vyprázdní odkazy na ně.
     */
    private void prepareGrid()
    {
        if (showGrid  &&  (step > 1))
        {
            if ((verticals == null)  ||  (verticals.length != fieldWidth)) {
                verticals = new Line[fieldWidth];
            }
            if ((horizontals == null)  ||  (horizontals.length != fieldHeight)) {
                horizontals = new Line[fieldHeight];
            }
            for (int i=0, x=step;   i < fieldWidth;      i++, x+=step) {
                verticals[i] = new Line(x, 0, x, pixelHeight, gridColor);
            }
            for (int i=0, y=step;   i < fieldHeight;   i++, y+=step) {
                horizontals[i] = new Line(0, y, pixelWidth, y, gridColor);
            }
        }
        else
        {
            //Uvolnění doposud používaných instancí
            verticals   = null;
            horizontals = null;
            showGrid    = false;
        }
    }


    /***************************************************************************
     * Vytvoří a připraví okno, které pak bude správce používat.
     * Možná je implementace zbytečně rafinovaná,
     * ale před tím se to nechutně často kousalo.
     *
     * @return Připravené okno
     */
    private JFrame prepareWindow()
    {
        final JFrame[] oo = new JFrame[1];
        Runnable r = new Runnable() {
            @Override public void run()
            {
                JFrame jfOkno = new JFrame(); //Vytvoří nové aplikační okno
                jfOkno.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                jfOkno.setTitle("POLOTOVAR");
                //Zavřením okna se zavře celá aplikace
                oo[0] = jfOkno;
            }
        };
        try {
            javax.swing.SwingUtilities.invokeAndWait(r);
        } catch (Exception ex) {
            String s = "Vytvoření okna se nepodařilo";
            System.err.println(s);
            JOptionPane.showMessageDialog(null, s);
            System.exit(1);
        }
//%I+ @DB_CM
//        System.out.println("Vytvoření okna se zdařilo (snad)");
//%I-

        return oo[0];
    }



//== EMBEDDED TYPES AND INNER CLASSES ==========================================
//== THE TESTS =================================================================
//%A+ <"

    private static void w() {
        try{
            Thread.sleep(10);
        }
        catch(InterruptedException e){}
    }


    /***************************************************************************
     * Testovaci metoda
     */
    public static void test() {
        CM.add(new Rectangle());                    w();
        CM.add(new Ellipse(), new Triangle());   w();
        IO.inform("Výchozí obrázek - budu vyjímat výřez");
        Picture obr = CM.getPicture(50, 0, 75, 75);   w();
        CM.add(obr);                               w();
        CM.setBackgroundColor(NamedColor.BLACK);               w();
        IO.inform("Obrázek přidaný?");
        obr.setPosition(100, 50);                       w();
        IO.inform("Posunutý?");
        obr.setSize(150, 150);                        w();
        IO.inform("Zvětšený?");
        CM.setStepSize(50, 5, 2);
//        CM.setStepSize(1, 50, 50);
//        CM.saveAsPicture(new File("D:/SMAZAT.PNG"));

        System.exit(0);
    }


    /***************************************************************************
     * Testovaci metoda
     */
    public static void test2()
    {
        IO.inform("Hotovo");
        CM.add(new Rectangle   (0, 0, 300, 300));
        CM.add(new Ellipse     (0, 0, 300, 300));
        CM.add(new Triangle(0, 0, 300, 300));
        IO.inform("Nakresleno");

        System.exit(0);
    }
    /** @param args Paremtry příkazového řádku - nepoužité  */
    public static void main(String[] args) { test2(); } /**/
//%A-
}

//%%- ---- Start of the ovejumped text with closing of the envelope ----
////////////////////////////////////////////////////////////////////////////////
//== TESTING CLASSES AND METHODS ===============================================

    /***************************************************************************
     * Test method.
     */
    public static void test()
    {
//        Run.tests(Test.class,
//                new TearDown(),
//                "testFixture",
//                "");
        IO.inform("After you feast your eyes, press OK");
        System.exit(0);
    }
    /** @param args Command line arguments - they are not used. */
    public static void main(String[] args)  {  test();  }
}
